[BZOJ 3530] [Sdoi2014] 数数 【AC自动机+DP】

题目链接:BZOJ - 3530

 

题目分析

明显是 AC自动机+DP,外加数位统计。

WZY 神犇出的良心省选题,然而去年我太弱..比现在还要弱得多..

其实现在做这道题,我自己也没想出完整解法..

就想出了个 O(l^3) 的做法:

完全按照数位统计的思想来,先统计长度不足 len 的数字的合法种类数,这个枚举开头,然后 AC 自动机 DP 一下,用 f[i][j] 表示到了第 i 位,在第 j 个节点上的合法数字个数。这样是 O(L^2)。

然后长度等于 n 的部分,就按照数位统计,一位位向后推,然后每次 O(L^2) DP 求,这样总的复杂度是 O(L^3)。

注意不能走包含模式串的节点,首先 Trie 中模式串的节点肯定不能走,其次,如果沿着一个节点的 Fail 链一直向上走,只要链上有一个节点是不能走的,那么这个节点也就不能走了。

所以需要从每个的节点开始,逆着 Fail 的边 DFS 一下,如果当前状态下将经过的点全都 Ban 掉。但是数据好像比较弱,不加这个也是不会出问题的。

但是这样会弄错一种情况:比如有 123, 2 这两个串,然后它就沿着 -> 1 -> 2 一直向后匹配,没有匹配成功 123 ,但是其实已经包含了 2 这个串。

这样的复杂度只有70分。

正解的复杂度是 O(L^2) 的:对于长度不足 len 的数字,还是直接求,O(L^2)。

对于长度等于 len 的数字,我们就直接用一个 O(L^2) 的 DP 求出来,我们把状态增加一维, g[i][j][k] ,前两位是第 i 位,走到第 j 个节点,k 是一个 [0/1] 变量,表示这个状态是否是沿着 n 的每一位走过来。

这样我们就知道了,如果当前状态是依照 n 的每一位走过来,那么下一步就只能转移 [0, n[i+1]] 这些数字。否则可以转移所有的数字。这样就求出了小于等于 n 的所有合法数字。

总的复杂度是 O(L^2) 。

 

AC自动机+DP的一般做法:f[i][j] 表示走了 i 步,走到了第 j 个节点,有时还需要加第 3 维或更多。

然后 DP 的时候就是 枚举 i ,枚举 j,枚举转移到下一步的字符。

 

代码

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <queue>

using namespace std;

const int MaxL = 1500 + 5, MaxNode = 15000 + 5, Mod = 1000000007;

int len, l, m, Index, Ans;
int f[MaxL][MaxNode], g[MaxL][MaxNode][2];

char S[MaxL], Ns[MaxL];

struct Trie
{
	int x, Idx;
	bool Ban;
	Trie *Fail, *Child[10];
} TA[MaxNode], *P = TA, *Root, *Zero;

Trie* NewNode(int Num = 0)
{
	++P;
	P -> x = Num;
	P -> Idx = ++Index;
	P -> Fail = NULL;
	P -> Ban = false;
	memset(P -> Child, 0, sizeof(P -> Child));
	return P;
}

void Insert(char *S, int l)
{
	int t;
	Trie *Now = Root;
	for (int i = 1; i <= l; ++i)
	{
		t = S[i] - '0';
		if (Now -> Child[t] == NULL)
			Now -> Child[t] = NewNode(t);
		Now = Now -> Child[t];
	}
	Now -> Ban = true;
}

struct Edge
{
	int v;
	Edge *Next;
} E[MaxNode], *PE = E, *Point[MaxNode];

inline void AddEdge(int x, int y)
{
	++PE; PE -> v = y;
	PE -> Next = Point[x]; Point[x] = PE;
}

bool Visit[MaxNode];

void DFS(int x, bool f)
{
	Visit[x] = true;
	TA[x].Ban = (TA[x].Ban) || f;
	for (Edge *j = Point[x]; j; j = j -> Next)
		DFS(j -> v, TA[x].Ban);
}

queue<Trie *> Q;

void Build_Fail()
{
	while (!Q.empty()) Q.pop();
	Q.push(Root);
	Trie *Now;
	while (!Q.empty())
	{
		Now = Q.front(); Q.pop();
		AddEdge(Now -> Fail -> Idx, Now -> Idx);
		for (int i = 0; i <= 9; ++i)
		{
			if (Now -> Child[i] == NULL) Now -> Child[i] = Now -> Fail -> Child[i];
			else 
			{
				Now -> Child[i] -> Fail = Now -> Fail -> Child[i];
				Q.push(Now -> Child[i]);
			}
		}
	}
	for (int i = 1; i <= Index; ++i)
		if (!Visit[i]) DFS(i, TA[i].Ban);
}

void Usual_Disco()
{
	memset(f, 0, sizeof(f));
	for (int i = 1; i <= 9; ++i)
		if (Root -> Child[i] -> Ban == false)
			++f[1][Root -> Child[i] -> Idx];
	for (int i = 1; i < len; ++i)
		for (int j = 1; j <= Index; ++j)
		{
			if (TA[j].Ban) continue;
			Ans = (Ans + f[i][j]) % Mod;
			for (int t = 0; t <= 9; ++t)
				if (!TA[j].Child[t] -> Ban)
				{
					f[i + 1][TA[j].Child[t] -> Idx] += f[i][j];
					f[i + 1][TA[j].Child[t] -> Idx] %= Mod;
				}
		}
	memset(g, 0, sizeof(g));
	for (int i = 1; i < Ns[1] - '0'; ++i)
		if (Root -> Child[i] -> Ban == false)
			++g[1][Root -> Child[i] -> Idx][0];
	if (Root -> Child[Ns[1] - '0'] -> Ban == false)
		++g[1][Root -> Child[Ns[1] - '0'] -> Idx][1];
	for (int i = 1; i <= len; ++i)
		for (int j = 1; j <= Index; ++j)
		{
			if (TA[j].Ban) continue;
			if (i == len) 
			{
				Ans = (Ans + g[i][j][0]) % Mod;
				Ans = (Ans + g[i][j][1]) % Mod;
				continue;
			}
			for (int t = 0; t <= 9; ++t)
				if (!TA[j].Child[t] -> Ban)
				{
					g[i + 1][TA[j].Child[t] -> Idx][0] += g[i][j][0];
					g[i + 1][TA[j].Child[t] -> Idx][0] %= Mod;
				}
			for (int t = 0; t < Ns[i + 1] - '0'; ++t)
				if (!TA[j].Child[t] -> Ban)
				{
					g[i + 1][TA[j].Child[t] -> Idx][0] += g[i][j][1];
					g[i + 1][TA[j].Child[t] -> Idx][0] %= Mod;
				}
			if (!TA[j].Child[Ns[i + 1] - '0'] -> Ban)
			{
				g[i + 1][TA[j].Child[Ns[i + 1] - '0'] -> Idx][1] += g[i][j][1];
				g[i + 1][TA[j].Child[Ns[i + 1] - '0'] -> Idx][1] %= Mod;
			}
		}
}

int main()
{
	scanf("%s", Ns + 1);
	len = strlen(Ns + 1);
	scanf("%d", &m);
	Root = NewNode();
	Zero = NewNode();
	Root -> Fail = Zero;
	for (int i = 0; i <= 9; ++i) Zero -> Child[i] = Root;
	for (int i = 1; i <= m; ++i)
	{
		scanf("%s", S + 1);
		l = strlen(S + 1);
		Insert(S, l);
	}
	Build_Fail();
	Usual_Disco();
	Ans %= Mod;
	cout << Ans << endl;
	return 0;
}

  

posted @ 2015-04-26 11:19  JoeFan  阅读(348)  评论(0编辑  收藏  举报